Workspaces
A workspace is a way to group multiple related crates (binary and/or library) into one logical project.
Key idea:
A workspace lets multiple crates share the same Cargo.lock, target directory, and dependency versions.
So instead of one big crate, you build many small crates that work together.
Why Workspaces Exist (Real Reason)
Without workspaces, large projects become:
- tightly coupled
- hard to test independently
- slow to compile
- messy to reuse
Workspaces solve this by:
- separating concerns into crates
- allowing parallel compilation
- enabling clean boundaries
- sharing dependencies efficiently
What a Workspace Is NOT
- A workspace is not a crate
- A workspace has no code itself
- A workspace does not compile to a binary or library
It is just an organizational layer.
Basic Workspace Structure
my_workspace/
├── Cargo.toml // workspace manifest
├── Cargo.lock
├── target/
├── app/
│ ├── Cargo.toml
│ └── src/
│ └── main.rs
└── core/
├── Cargo.toml
└── src/
└── lib.rs
app→ binary cratecore→ library crate
Workspace Cargo.toml
Root Cargo.toml
[workspace]
members = [
"app",
"core"
]
- This file defines the workspace
- No
[package]section - Only workspace configuration
Library Crate Inside Workspace
core/Cargo.toml
[package]
name = "core"
version = "0.1.0"
edition = "2021"
core/src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
Binary Crate Using Workspace Library
app/Cargo.toml
[package]
name = "app"
version = "0.1.0"
edition = "2021"
[dependencies]
core = { path = "../core" }
app/src/main.rs
use core::add;
fn main() {
println!("{}", add(3, 4));
}
coreis imported like a normal cratepathpoints to workspace member- No publishing required
Shared Cargo.lock and target/
In a workspace:
- all crates share one Cargo.lock
- all compiled artifacts go to one target/
Benefits:
- consistent dependency versions
- faster builds
- less disk usage
Running & Building in a Workspace
Build everything
cargo build
Run a specific binary
cargo run -p app
Test one crate
cargo test -p core
Workspace with Multiple Binaries
my_workspace/
├── Cargo.toml
├── api/
├── cli/
└── shared/
api→ backend servicecli→ command-line toolshared→ common logic
All depend on shared.
Workspaces + Modules (How They Differ)
| Concept | Purpose |
|---|---|
| Module | Organize code inside a crate |
| Crate | Compilation unit |
| Workspace | Organize multiple crates |
Rule of thumb:
- small separation → module
- strong boundary → crate
- multiple apps/libs → workspace
When Should You Create a Workspace?
Use a workspace when:
- you have multiple binaries
- you want shared libraries
- compile time is growing
- you plan to publish some crates
- different teams own different parts
Real-World Example (Production Style)
ecommerce/
├── Cargo.toml
├── auth/
├── payments/
├── inventory/
├── web/
└── common/
web→ frontend server (binary)auth,payments,inventory→ servicescommon→ shared types & utilities
Each crate:
- has its own
lib.rsormain.rs - its own dependencies
- clear API boundaries
Workspace Dependency Inheritance (Advanced)
Root Cargo.toml
[workspace.dependencies]
serde = "1.0"
Member crate
[dependencies]
serde = { workspace = true }
This ensures:
- same version everywhere
- no duplication
- easy upgrades
Publishing from a Workspace
You can:
- publish individual crates
- keep others private
- share internal crates via
path - Workspace does not affect publishing.
Common Workspace Mistakes
- Putting code in root
- One crate with too many responsibilities
- Circular dependencies between crates
- Making everything a workspace too early
Best Practices
- Start with one crate
- Extract crates when boundaries appear
- Keep crates small and focused
- Use pub(crate) for internal APIs
- Re-export clean APIs from libraries
Mental Model (Very Important)
Think of Rust structure like this:
Workspace
└── Crates
└── Modules
└── Items
Each layer has a clear responsibility.